// Head Customer Agent
//
// This function represents the logic and activities of a Head Customer in the movie theater simulation.
// This function will be forked onto a thread during initialization.
//

#include "HeadCustomerAgent.h"
#include "CustomerAgent.h"
#include "globalsExtern.cc"
#include "utility.h"

void HeadCustomerAgentExecute(int arg) {
	// Head Customer Agent local data goes here
	int GroupID = arg;
	int numTickets;
	int price;
	TicketStub* stub;
	int numPopcorn = 0;
	int numSoda = 0;
	int myLine = 0;

	// Head Customer Agent running code should go here.
	DEBUG('H', "%s: Thread now running.\n", currentThread->getName());
	
	
	// Get into the ticket lines
	ticketLineSelectionLock->Acquire();
	myLine = ticketLineSelectionWhiteboard->selectShortestLine();
	// If the current shortest line has no one in line, take up the clerk
	if (ticketLineSelectionWhiteboard->getLineLength(myLine) == 0) {
		DEBUG('H', "%s: Entering an empty ticket clerk line, signaling clerk.\n", currentThread->getName());
		ticketClerkCustomerLock[myLine]->Acquire();
		ticketClerkCondition[myLine]->Signal(ticketClerkCustomerLock[myLine]);
		ticketClerkCustomerLock[myLine]->Release();
	}
	// Get in line, increment the line length
	DEBUG('R', "%s is getting in TicketClerk line %d.\n", currentThread->getName(), myLine);
	DEBUG('H', "%s is getting in TicketClerk line %d.\n", currentThread->getName(), myLine);
	ticketLineSelectionWhiteboard->incrementLineLength(myLine);
	ticketLineSelectionCondition[myLine]->Wait(ticketLineSelectionLock);
	// Got woken up and left the line, decrement the line length
	ticketLineSelectionWhiteboard->decrementLineLength(myLine);

	// Might have gotten woken up by a Broadcast from the manager informing
	// us there a clerk came off break (new line open). Requeue ourselves
	// instead of proceding to the clerk.
	while(ticketLineSelectionWhiteboard->getNumReshuffle() != 0) {
		DEBUG('H', "%s: I have been reshuffled!\n", currentThread->getName());
	   myLine = ticketLineSelectionWhiteboard->selectShortestLine();
	   DEBUG('H', "%s: I have chosen line %d. It has a length of %d.!\n", currentThread->getName(), myLine, ticketLineSelectionWhiteboard->getLineLength(myLine));
	   // If the current shortest line has no one in line, take up the clerk
	   if (ticketLineSelectionWhiteboard->getLineLength(myLine) == 0) {
		  ticketClerkCustomerLock[myLine]->Acquire();
		  DEBUG('H', "%s: Entering an empty ticket clerk line, signaling clerk.\n", currentThread->getName());
		  ticketClerkCondition[myLine]->Signal(ticketClerkCustomerLock[myLine]);
		  ticketClerkCustomerLock[myLine]->Release();
	   }
	   // Decrement reshuffle (we are now in the correct line)
	   ticketLineSelectionWhiteboard->setNumReshuffle(ticketLineSelectionWhiteboard->getNumReshuffle()-1) ;
	   // Get in line, increment the line length
	   DEBUG('H', "%s: Increasing line length on %d.\n", currentThread->getName(), myLine);
	   ticketLineSelectionWhiteboard->incrementLineLength(myLine);
	   DEBUG('H', "%s: waiting in line.\n", currentThread->getName());
	   ticketLineSelectionCondition[myLine]->Wait(ticketLineSelectionLock);
	   // Got woken up and left the line, decrement the line length
	   DEBUG('H', "%s: Awake after being refhuffled. Decrementing line length. line:%d.\n", currentThread->getName(), myLine);
	   ticketLineSelectionWhiteboard->decrementLineLength(myLine);
	}

	// Got woken up and left line, proceding to the clerk kiosk
	ticketLineSelectionLock->Release();

	//We got the ok from the clerk, head over to the Kiosk.
	ticketClerkCustomerLock[myLine]->Acquire();
	DEBUG('H', "%s: Entering the monitor.\n", currentThread->getName());
	// Decide how many tickets this customer group wants (should be group size)
	numTickets = howManyTicketsDoIWant(GroupID);
	DEBUG('R', "%s is walking up to TicketClerk %d to buy %d tickets.\n", currentThread->getName(),myLine,numTickets);
	
	DEBUG('H', "%s: I want %d tickets.\n", currentThread->getName(), numTickets);
	// Write our request for tickets on the whiteboard.
	ticketClerkCustomerWhiteboard[myLine]->setNumTickets(numTickets);
	// Signal the clerk and wait.
	ticketClerkCondition[myLine]->Signal(ticketClerkCustomerLock[myLine]);
	DEBUG('H', "%s: Woke up Clerk, waiting in CV.\n", currentThread->getName());
	ticketCustomerCondition[myLine]->Wait(ticketClerkCustomerLock[myLine]);
	
	// Clerk responded with the price of our request.
	price = ticketClerkCustomerWhiteboard[myLine]->getTotalPrice();
	// Pay the specified amount
	ticketClerkCustomerWhiteboard[myLine]->setMoneyGiven(price);
	// Signal the clerk and wait
	ticketClerkCondition[myLine]->Signal(ticketClerkCustomerLock[myLine]);
	DEBUG('R', "%s in TicketClerk line %d is paying %d for tickets.\n", currentThread->getName(), myLine, price);
	ticketCustomerCondition[myLine]->Wait(ticketClerkCustomerLock[myLine]);
	
	// Clerk printed our ticket, grab it off whiteboard.
	stub = ticketClerkCustomerWhiteboard[myLine]->getTicketStub();
	if (!stub) {
		DEBUG('H', "%s: ERROR: found no Ticket Stub.\n", currentThread->getName());
	}
	
	// Signal the clerk that we have our ticket and are moving on.
	DEBUG('H', "%s: Waking up Clerk.\n", currentThread->getName());
	DEBUG('R',"%s is in the lobby", currentThread->getName());
	ticketClerkCondition[myLine]->Signal(ticketClerkCustomerLock[myLine]);
	ticketClerkCustomerLock[myLine]->Release();
	DEBUG('R', "%s is leaving TicketClerk %d.\n",currentThread->getName(),myLine);
	DEBUG('H', "%s: Leaving the monitor.\n", currentThread->getName());
	
	// Now that we are in the lobby, discuss with the group who wants food.
	DEBUG('H', "%s: Entering group monitor.\n", currentThread->getName());
	groupLock[GroupID]->Acquire();
	resetGroupWhiteboard(GroupID);
	groupWhiteboard[GroupID]->setState(0); // FOOD state
	incrementFood(GroupID);    // Head customer puts in the first order.
	DEBUG('H', "%s: Broadcasting to group. Going to sleep.\n", currentThread->getName());
	// Wake up all group members asking them to put in food orders, unless group size is only 1
	if (groupWhiteboard[GroupID]->getGroupSize() > 1){
		groupCustomerCondition[GroupID]->Broadcast(groupLock[GroupID]);
		headCustomerCondition[GroupID]->Wait(groupLock[GroupID]);
	}
	
	// All group memebers have reported their food orders
	numPopcorn = groupWhiteboard[GroupID]->getPopcornTally();
	numSoda = groupWhiteboard[GroupID]->getSodaTally();
	DEBUG('H',"%s: Group wants %d popcorn and %d soda.\n", currentThread->getName(), numPopcorn, numSoda);
	DEBUG('H',"%s: Leaving group monitor.\n", currentThread->getName());
	groupLock[GroupID]->Release();

	
	
	// Get into the food lines after resetting myLine
	foodLineSelectionLock->Acquire();
	myLine = 0;
	myLine = foodLineSelectionWhiteboard->selectShortestLine();
	// If the current shortest line has no one in line, take up the clerk
	if (foodLineSelectionWhiteboard->getLineLength(myLine) == 0) {
		DEBUG('H', "%s: Entering an empty food clerk line, signaling clerk.\n", currentThread->getName());
		foodClerkCustomerLock[myLine]->Acquire();
		foodClerkCondition[myLine]->Signal(foodClerkCustomerLock[myLine]);
		foodClerkCustomerLock[myLine]->Release();
	}
	// Get in line, increment the line length
	DEBUG('R', "%s is getting in FoodClerk line %d\n",currentThread->getName(),myLine);
	foodLineSelectionWhiteboard->incrementLineLength(myLine);
	foodLineSelectionCondition[myLine]->Wait(foodLineSelectionLock);
	// Got woken up and left the line, decrement the line length
	foodLineSelectionWhiteboard->decrementLineLength(myLine);

	// Might have gotten woken up by a Broadcast from the manager informing
	// us there a clerk came off break (new line open). Requeue ourselves
	// instead of proceding to the clerk.
	while(foodLineSelectionWhiteboard->getNumReshuffle() != 0) {
	   myLine = foodLineSelectionWhiteboard->selectShortestLine();
	   // If the current shortest line has no one in line, take up the clerk
	   if (foodLineSelectionWhiteboard->getLineLength(myLine) == 0) {
		  foodClerkCustomerLock[myLine]->Acquire();
		  foodClerkCondition[myLine]->Signal(foodClerkCustomerLock[myLine]);
		  foodClerkCustomerLock[myLine]->Release();
	   }
	   // Decrement reshuffle (we are now in the correct line)
	   foodLineSelectionWhiteboard->setNumReshuffle(foodLineSelectionWhiteboard->getNumReshuffle()-1) ;
	   // Get in line, increment the line length
	   foodLineSelectionWhiteboard->incrementLineLength(myLine);
	   foodLineSelectionCondition[myLine]->Wait(foodLineSelectionLock);
	   // Got woken up and left the line, decrement the line length
	   foodLineSelectionWhiteboard->decrementLineLength(myLine);
	}

	// Got woken up and left line, proceding to the clerk kiosk
	foodLineSelectionLock->Release();

	//We got the ok from the clerk, head over to the Kiosk.
	foodClerkCustomerLock[myLine]->Acquire();
	DEBUG('H', "%s: Entering the monitor.\n", currentThread->getName());
	DEBUG('R' ,"%s is walking up to FoodClerk %d to buy %d popcorn and %d soda.\n",currentThread->getName(),myLine,numPopcorn,numSoda);
	
	// Write our request for popcorn and soda on the whiteboard.
	foodClerkCustomerWhiteboard[myLine]->setNumPopcorn(numPopcorn);
	foodClerkCustomerWhiteboard[myLine]->setNumSoda(numSoda);
	// Signal the clerk and wait.
	foodClerkCondition[myLine]->Signal(foodClerkCustomerLock[myLine]);
	DEBUG('H', "%s: Woke up Clerk, waiting in CV.\n", currentThread->getName());
	foodCustomerCondition[myLine]->Wait(foodClerkCustomerLock[myLine]);
	
	// Clerk responded with the price of our request.
	price = foodClerkCustomerWhiteboard[myLine]->getTotalPrice();
	DEBUG('R', "%s in FoodClerk line %d is paying %d for food.\n", currentThread->getName(),myLine,price);
	// Pay the specified amount
	foodClerkCustomerWhiteboard[myLine]->setMoneyGiven(price);
	// Signal the clerk and wait
	foodClerkCondition[myLine]->Signal(foodClerkCustomerLock[myLine]);
	DEBUG('H', "%s: Paying $%d, woke up clerk.\n", currentThread->getName(), price);
	foodCustomerCondition[myLine]->Wait(foodClerkCustomerLock[myLine]);
	
	// Signal the clerk that we have our food and are moving on.
	DEBUG('H', "%s: Waking up Clerk.\n", currentThread->getName());
	foodClerkCondition[myLine]->Signal(foodClerkCustomerLock[myLine]);
	foodClerkCustomerLock[myLine]->Release();
	DEBUG('R', "%s is leaving FoodClerk %d.\n",currentThread->getName(),myLine);
	DEBUG('H', "%s: Leaving the monitor.\n", currentThread->getName());
	
	
	//get into the ticket taker lines after resetting myLine
	//DEBUG('H', "%s: Attempting to acquire the taker line lock.\n", currentThread->getName());
	takerLineSelectionLock->Acquire();
	//DEBUG('H', "%s: Acquired the taker line lock.\n", currentThread->getName());
	myLine = 0;
	myLine = takerLineSelectionWhiteboard->selectShortestLine();
	DEBUG('R',"%s  is leaving the lobby.", currentThread->getName());
	DEBUG('R',"%s is getting in TicketTaker line %d.\n",currentThread->getName(),myLine); 
	// If the current shortest line has no one in line, take up the clerk
	if (takerLineSelectionWhiteboard->getLineLength(myLine) == 0) {
		DEBUG('H', "%s: Entering an empty ticket taker line, signaling clerk.\n", currentThread->getName());
		ticketTakerCustomerLock[myLine]->Acquire();
		ticketTakerCondition[myLine]->Signal(ticketTakerCustomerLock[myLine]);
		ticketTakerCustomerLock[myLine]->Release();
	}
	// Get in line, increment the line length
	takerLineSelectionWhiteboard->incrementLineLength(myLine);
	takerLineSelectionCondition[myLine]->Wait(takerLineSelectionLock);
	// Got woken up and left the line, decrement the line length
	takerLineSelectionWhiteboard->decrementLineLength(myLine);

	// Might have gotten woken up by a Broadcast from the manager informing
	// us there a clerk came off break (new line open). Requeue ourselves
	// instead of proceding to the clerk.
	while(takerLineSelectionWhiteboard->getNumReshuffle() != 0) {
	   myLine = selectShortestTakerLine();
	   // If the current shortest line has no one in line, take up the clerk
	   if (takerLineSelectionWhiteboard->getLineLength(myLine) == 0) {
		  ticketTakerCustomerLock[myLine]->Acquire();
		  ticketTakerCondition[myLine]->Signal(ticketTakerCustomerLock[myLine]);
		  ticketTakerCustomerLock[myLine]->Release();
	   }
	   // Decrement reshuffle (we are now in the correct line)
	   takerLineSelectionWhiteboard->setNumReshuffle(foodLineSelectionWhiteboard->getNumReshuffle()-1) ;
	   // Get in line, increment the line length
	   takerLineSelectionWhiteboard->incrementLineLength(myLine);
	   takerLineSelectionCondition[myLine]->Wait(takerLineSelectionLock);
	   // Got woken up and left the line, decrement the line length
	   takerLineSelectionWhiteboard->decrementLineLength(myLine);
	}

	// Got woken up and left line, proceding to the clerk kiosk
	takerLineSelectionLock->Release();
	DEBUG('R',"%s has released takerLineSelectionLock.\n",currentThread->getName());
	
	//We got the ok from the ticket taker, head over to the Kiosk.
	ticketTakerCustomerLock[myLine]->Acquire();
	DEBUG('R', "%s is walking up to TicketTaker %d to give %d tickets.\n",currentThread->getName(),myLine,numTickets);
	DEBUG('H', "%s: Entering the monitor.\n", currentThread->getName());
	
	// Put our ticket stub on the whiteboard
	ticketTakerCustomerWhiteboard[myLine]->setTicketStub(stub);
	// Signal the ticket taker and wait.
	ticketTakerCondition[myLine]->Signal(ticketTakerCustomerLock[myLine]);
	//change back to H
	DEBUG('R', "%s: Woke up ticket taker, waiting in CV.\n", currentThread->getName());
	takerCustomerCondition[myLine]->Wait(ticketTakerCustomerLock[myLine]);
	
	// Signal the ticket taker that we are entering the theater.
	//change back to H
	DEBUG('R', "%s: Waking up ticket taker.\n", currentThread->getName());
	ticketTakerCondition[myLine]->Signal(ticketTakerCustomerLock[myLine]);
	ticketTakerCustomerLock[myLine]->Release();
	DEBUG('R', "%s is leaving TicketTaker %d.\n", currentThread->getName(),myLine);
	DEBUG('H', "%s: Leaving the monitor.\n", currentThread->getName());
	theaterSelectionLock->Acquire();
	//Group will now find seats in the theater
	selectTheaterSeats(GroupID);
	//theaterSeatedCustomerCondition->Signal(theaterSelectionLock);
	theaterSeatedCustomerCondition->Wait(theaterSelectionLock);
	theaterSelectionLock->Release();
	
	// Now that we are in the lobby, discuss with the group who need to use the bathroom.
	DEBUG('H', "%s: Entering group monitor to ask about Bathroom.\n", currentThread->getName());
	groupLock[GroupID]->Acquire();
	resetGroupWhiteboard(GroupID);
	groupWhiteboard[GroupID]->setState(1); // BATHROOM state
	int meBathroom = wantsToGoToBathroom(GroupID);    // Head customer puts in the first order.
	DEBUG('H', "%s: Broadcasting to group. Going to sleep.\n", currentThread->getName());
	// Wake up all group members asking them to put in bathroom orders, unless group size is only 1
	if (groupWhiteboard[GroupID]->getGroupSize() > 1){
		groupCustomerCondition[GroupID]->Broadcast(groupLock[GroupID]);
		headCustomerCondition[GroupID]->Wait(groupLock[GroupID]);
	}
	
	// All group memebers have reported their food orders
	if (groupWhiteboard[GroupID]->getSomeoneNeedsBathroom()) {
		// someone is in the bathroom, simulate the time.
		for (int i = 0; i < 10; i++) {
			currentThread->Yield();
		}
	}
	
	if (meBathroom == 1) {
		DEBUG('R', "%s: Left the Bathroom\n",currentThread->getName());
	}
	
	groupWhiteboard[GroupID]->setState(2); // DONE state
	// Wake everyone up to tell them its time to leave
	groupCustomerCondition[GroupID]->Broadcast(groupLock[GroupID]);
	groupLock[GroupID]->Release();
	
	DEBUG('R', "%s: Now leaving the movie theater\n",currentThread->getName());
	DEBUG('H', "%s: Thread finished.\n",currentThread->getName());
}

int howManyTicketsDoIWant(int GroupID) {
	int tickets = groupWhiteboard[GroupID]->getGroupSize();
	return tickets;
}

int selectShortestTakerLine() {
   int numLines = takerLineSelectionWhiteboard->getNumLines();
   int currShortest = 100;
   int currShortestInd = -1;
   for (int i = 0; i < numLines; i++) {
		if (takerLineSelectionWhiteboard->getLineLength(i) < currShortest) {
			currShortestInd = i;
			currShortest = takerLineSelectionWhiteboard->getLineLength(i);
		}
   }
   return currShortestInd;
}

void resetGroupWhiteboard(int GroupID) {
	groupWhiteboard[GroupID]->setPopcornTally(0);
	groupWhiteboard[GroupID]->setSodaTally(0);
	groupWhiteboard[GroupID]->setBathroomTally(0);
	groupWhiteboard[GroupID]->setNumModified(0);
}

//assumption: HeadCustomerAgent has already acquired lock TheaterSelectionLock before entering this function
void selectTheaterSeats(int GroupID){
	int numRows = theaterSelectionWhiteboard->getNumRows();
	int seatsPerRow = theaterSelectionWhiteboard->getSeatsPerRow();
	int groupSize = groupWhiteboard[GroupID]->getGroupSize();	
	int seatNum = 0;
	
	DEBUG('D', "NR=%d SPR=%d GS=%d SN=%d\n",numRows,seatsPerRow,groupSize,seatNum);

	//For statement checks to see if each row has enough available seats to fit the entire group
	for(int i=0; i<numRows; i++){
		DEBUG('D', "Row=%d SA=%d\n",i,theaterSelectionWhiteboard->getRowSeatsAvail(i));
		if(groupSize <= theaterSelectionWhiteboard->getRowSeatsAvail(i)){
			for(int j=0; j<groupSize; j++){
				seatNum = seatsPerRow - theaterSelectionWhiteboard->getRowSeatsAvail(i);
				DEBUG('R', "Customer %d in Group %d has found the following seat: row %d and seat %d.\n", j, GroupID, i, seatNum);
				theaterSelectionWhiteboard->decrementRowSeatsAvail(i);
			}
			return;
		}
	}
	
	//If no row has enough seats for the entire group, then this will find the biggest openings and split the group up in the biggest groups
	int groupCount = 0;
	while (groupCount < groupSize){
	int row = theaterSelectionWhiteboard->findRowWithMostSeatsAvail();
		if(groupSize > theaterSelectionWhiteboard->getRowSeatsAvail(row)){
			for(int i=0; i<theaterSelectionWhiteboard->getRowSeatsAvail(row); i++){
				seatNum = seatsPerRow - theaterSelectionWhiteboard->getRowSeatsAvail(row);
				DEBUG('R', "Customer %d in Group %d has found the following seat: row %d and seat %d.\n", groupCount, GroupID, row, seatNum);
				theaterSelectionWhiteboard->decrementRowSeatsAvail(row);
				groupCount++;
			}
		}
		else{
			for(int i=0; i<(groupSize-groupCount); i++){
				seatNum = seatsPerRow - theaterSelectionWhiteboard->getRowSeatsAvail(row);
				DEBUG('R', "Customer %d in Group %d has found the following seat: row %d and seat %d.\n", groupCount, GroupID, row, seatNum);
				theaterSelectionWhiteboard->decrementRowSeatsAvail(row);
				groupCount++;
				return;
			}
		}
	}
	return;
}
